import upload from '../store/modules/upload'
import { CONTENT_TYPE_MAP, IMAGE_TYPE } from './constance'
import { checkImgBySuffix, transUrl } from './cos'
import { ElMessage, UploadStatus } from 'element-plus'
import { get } from 'lodash'
import http from './http'
import { newDate } from '@/common/utils/common'

const qiniuTokenUrl = 'base/action/qiniu/token'
const store: any = upload()
export interface MimeTypes {
  title: string
  extensions: string
}

export interface Filter {
  mime_types: MimeTypes
  max_file_size: string
  prevent_duplicates: boolean
}

export interface Config {
  http_method?: 'PUT' | 'POST' | 'GET' | 'DELETE'
  feature?: Record<string, unknown>
  browse_button?: string | Element
  init?: any
  initMap?: Record<string, unknown>
  multi_number?: number
  max_file_size?: string //20mb
  multipart_params?: Record<string, unknown>
  headers?: Record<string, unknown>
  features?: Record<string, unknown>
  useContentType?: boolean
  filter?: Filter
  preDomain?: string
  file?: ExtendFile
  isUseQiniu?: boolean
  fileType: string
}

export interface ExtendFile extends File {
  mSize?: number
  id?: any
  uid?: number
  putUrl?: string
  cosUrl?: string
  url?: string
  percent?: number
  fileKey?: string
  status: UploadStatus
}

// 此方法抽取plupload核心功能并对批量上传方法进行修改，如果后续想用plupload的其他功能可以往这里面加https://www.plupload.com/docs/v2/Uploader

export default class Upload {
  [key: string]: any
  constructor(config: Config) {
    config = {
      ...config,
      initMap: {
        ...(config.init || {})
      }
    }
    config.init = this.initInputChange
    Object.entries(config).forEach(([key, val]) => {
      this[key] = val
    })
    this.http_method = config.http_method || 'PUT'
    this.features = config.features || {}
  }

  static FAILED = 4

  static FILE_SIZE_ERROR = -600

  static FILE_EXTENSION_ERROR = -601

  static HTTP_ERROR = -200

  static SECURITY_ERROR = -400

  static GENERIC_ERROR = -100

  static IO_ERROR = -300

  static INIT_ERROR = -500

  static filterParams(params: any) {
    return Object.keys(params)
      .filter((value) => value.startsWith('x:'))
      .map((k) => [k, params[k].toString()])
  }

  static async getUploadToken(type: string, fileName: string) {
    const res = await http({
      url: qiniuTokenUrl,
      method: 'GET',
      params: {
        type,
        fileName
      }
    })
    const key = res.data.scope.split(':')[1]
    return {
      uptoken: res.data.uptoken,
      key
    }
  }

  getBrowseBtn() {
    return typeof this.browse_button === 'string'
      ? document.querySelector(`#${this.browse_button}`)
      : this.browse_button
  }

  initInputChange() {
    if (this.hasInput()) {
      return
    }
    this.input = document.createElement('input')
    this.input.type = 'file'
    if (this.multi_selection) {
      this.input.multiple = 'multiple'
    }
    this.input.accept = this.getFileAccept()
    this.input.style.display = 'none'
    this.input.className = 'upload_file_input'
    const btn = this.getBrowseBtn()
    btn.parentNode.insertBefore(this.input, btn.nextSibling)
    btn.addEventListener('click', this.makeInputChange)
    this.input.addEventListener('change', this.onInputChange)
  }

  async addFile(file: ExtendFile) {
    if (!this.files) {
      this.files = []
    }
    if (this.files.every((file: any) => get(file, 'name') !== file.name)) {
      const { putUrl } = await this.getUploadUrl(file.fileKey)
      file.putUrl = putUrl
      if (!this.multipart_params) {
        this.multipart_params = {}
      }
      this.files.push(file)
      setTimeout(() => this.start())
    }
  }

  async getUploadUrl(key: any) {
    const req = this.getOption('uptoken_url')
    req.data.key = key
    const res = await http({
      method: 'GET',
      params: req
    })
    return {
      putUrl: res.putUrl
    }
  }

  hasInput() {
    const btn = this.getBrowseBtn()
    return btn.parentNode.querySelectorAll('.upload_file_input')[0]
  }

  getFileAccept() {
    if (!Object.keys(this.filters || {}).length) {
      return ''
    }
    const { mime_types: mimeTypes } = this.filters
    const filterMimeArr = mimeTypes
      .map((i: any) => i.extensions)
      .join()
      .split(',')
    return filterMimeArr.map((i: any) => (CONTENT_TYPE_MAP as any)[i]).join()
  }

  makeInputChange() {
    this.input.click()
  }

  filesToArr(fileList: FileList) {
    const len = fileList.length
    const arr = []
    for (let i = 0; i < len; i++) {
      arr.push(fileList.item(i))
    }
    arr.length = len
    return arr
  }

  onInputChange(e?: any) {
    this.fileEvent = e
    if (this.isUploading) {
      const tipMap: any = {
        zh_CN: '正在进行文件上传，请稍后再试',
        zh_TW: '正在進行文件上傳，請稍後再試',
        en: 'Is being uploaded. Please try again later'
      }
      ElMessage.error(tipMap[localStorage.getItem('lang') || 'zh_CN'])
      return
    }
    this.files = Array.from(this.input.files)
    const acceptStr = this.getFileAccept()
    const notAcceptFile: any = this.files.find((i: ExtendFile) => !acceptStr.includes(this.getSuffix(i.name)))
    if (notAcceptFile && acceptStr) {
      e.target.value = null
      this.trigger('Error', {
        file: notAcceptFile,
        code: -601
      })
      return
    }
    this.files.forEach((i: ExtendFile) => {
      i.mSize = i.size / (1024 * 1024)
    })
    if (this.max_file_size) {
      const overSizeFile = this.files.find((i: ExtendFile) => (i.mSize || 0) > +this.max_file_size.split('mb')[0])
      if (overSizeFile) {
        e.target.value = null
        this.trigger('Error', {
          file: overSizeFile,
          code: -600
        })
        return
      }
      this.files = this.files.filter((i: ExtendFile) => (i.mSize || 0) <= +this.max_file_size.split('mb')[0])
    }
    if (this.multi_number) {
      this.files = this.files.filter((i: ExtendFile, m: number) => m < this.multi_number)
    }
    this.onFilter()
    this.trigger('FilesAdded', this.files)
  }

  onFilter() {
    if (!this.filter) {
      return
    }
    const { mime_types: mimeTypes, max_file_size: maxFileSize, prevent_duplicates: preventDuplicates } = this.filter
    const filterMimeStr = mimeTypes.map((i: MimeTypes) => i.extensions).join()
    if (mimeTypes) {
      this.files = this.files.filter((i: ExtendFile) => filterMimeStr.includes(this.getSuffix(i.name)))
    }
    if (maxFileSize) {
      this.files = this.files.filter((i: ExtendFile) => i.size < maxFileSize)
    }
    if (preventDuplicates) {
      this.files = this.files.reduce((pre: ExtendFile[], cur: ExtendFile) => {
        if (pre.length === 0) {
          pre.push(cur)
        } else {
          pre.every((i) => i.id !== cur.id) && pre.push(cur)
        }
        return pre
      }, [])
    }
  }

  async onUploadFiles() {
    for (const file of this.files) {
      const newFile = await this.trigger('BeforeUpload', file)
      this.isUseQiniu ? await this.qiniuUpload(newFile) : await this.onUpload(newFile)
    }
  }

  buildUrl(url: string, items: any[]) {
    let res = url
    let query = ''

    Object.entries(items).forEach(([name, value]) => {
      query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value)
    })

    if (query) {
      res += (res.indexOf('?') > 0 ? '&' : '?') + query
    }

    return res
  }

  getSuffix(url: string) {
    const cmpUrl = url.split('?')[0]
    const list = cmpUrl.split('.')
    return list[list.length - 1]
  }

  async qiniuUpload(file: ExtendFile) {
    let qiniuKey: any = null,
      qiniuToken: any = null
    if (this.isUseQiniu) {
      const { key, uptoken } = await Upload.getUploadToken(this.fileType, file.name)
      qiniuKey = key
      qiniuToken = uptoken
    }
    return new Promise((resolve, reject) => {
      // eslint-disable-next-line no-undef
      const xhr = new XMLHttpRequest()
      const form = new FormData()
      form.append('file', file)
      form.append('token', qiniuToken)
      form.append('key', qiniuKey)
      form.append('fname', file.name)
      if (file.fileKey) {
        this.multipart_params.key = file.fileKey
      }
      xhr.open('post', store.fileDomain.qiniuUploadDomain, true)
      this.isUploading = true
      file.status = 'ready'
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          const percent = Math.ceil((e.loaded / e.total) * 100)
          file.percent = percent
          file.status = 'uploading'
          this.trigger(
            'UploadProgress',
            {
              percent,
              lengthComputable: true,
              loaded: percent,
              total: 100
            },
            file,
            this.files
          )
        }
      }
      xhr.upload.onerror = (e: any) => {
        file.status = 'fail'
        this.trigger('Error', e, file, this.files)
      }
      xhr.send(form)
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4 && xhr.status === 200) {
          if (xhr.status === 200) {
            // 上传文件成功
            this.trigger('FileUploaded', xhr.responseText, file, this.files)
            this.trigger('UploadComplete', file, xhr.responseText)
            file.status = 'success'
            resolve(file)
          } else {
            // 上传文件失败
            file.status = 'fail'
            this.trigger('Error', new Error('上传失败'), file, this.files)
            reject()
          }
          this.fileEvent && this.fileEvent.target && (this.fileEvent.target.value = '')
          this.files = []
          this.isUploading = false
        }
      }
    })
  }

  async onUpload(file: ExtendFile) {
    return new Promise((resolve, reject) => {
      // eslint-disable-next-line no-undef
      const xhr = new XMLHttpRequest()
      if (file.fileKey) {
        if (!this.multipart_params) {
          this.multipart_params = {}
        }
        this.multipart_params.key = file.fileKey
      }
      file.url = `${file.cosUrl}${file.fileKey}`
      xhr.open(this.http_method, this.buildUrl(file.putUrl || '', this.multipart_params), true)
      const suffix = this.getSuffix(file.name)
      const contentType = (CONTENT_TYPE_MAP as any)[suffix] || file.type
      this.headers &&
        Object.entries(this.headers).forEach(([key, val]: any) => {
          xhr.setRequestHeader(key, val)
        })
      xhr.setRequestHeader('content-Type', `${contentType};charset=UTF-8`)
      this.isUploading = true
      file.status = 'ready'
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          const percent = Math.ceil((e.loaded / e.total) * 100)
          file.percent = percent
          file.status = 'uploading'
          this.trigger(
            'UploadProgress',
            {
              percent,
              lengthComputable: true,
              loaded: percent,
              total: 100
            },
            file,
            this.files
          )
        }
      }
      xhr.upload.onerror = (e: any) => {
        file.status = 'fail'
        this.trigger('Error', e, file, this.files)
      }
      xhr.send(file)
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4 && xhr.status === 200) {
          if (xhr.status === 200) {
            // 上传文件成功
            this.trigger('FileUploaded', xhr.responseText, file, this.files)
            resolve(file)
          } else {
            // 上传文件失败
            file.status = 'fail'
            this.trigger('Error', new Error('上传失败'), file, this.files)
            reject()
          }
          this.trigger('UploadComplete', file, xhr.responseText)
          this.fileEvent && this.fileEvent.target && (this.fileEvent.target.value = '')
          this.files = []
          this.isUploading = false
        }
      }
    })
  }

  async trigger(handleName: string, file: any, ...arg: (string | undefined | ExtendFile)[]) {
    // this.bindMap && this.bindMap[handleName] && this.bindMap[handleName](file, ...arg)
    if (this.initMap && this.initMap[handleName]) {
      return await this.initMap[handleName](file, ...arg)
    }
  }

  removeFile(file: ExtendFile) {
    this.files = this.files.filter((i: ExtendFile) => i.id !== file.id)
  }

  async start() {
    await this.onUploadFiles()
  }

  bind(handleName: string, cb: any) {
    if (!this.bindMap) {
      this.bindMap = {}
    }
    this.bindMap[handleName] = cb
  }

  getOption(name: string) {
    return this[name]
  }

  setOption(data: string | Record<string, unknown>, value: any) {
    if (typeof data === 'string') {
      this[data] = value
    } else {
      Object.entries(data).forEach(([key, val]) => {
        this[key] = val
      })
    }
  }

  // eslint-disable-next-line @typescript-eslint/no-empty-function
  destroy() {}
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  refresh() {}
}

export const km = {
  domain() {
    return store.fileDomain.cos7moorKmDomain
  },
  getKmUrl(oFile: any, options: any = {}) {
    // 获取km的链接
    const { useAttname = true, isOriginFile = false } = options
    let file = oFile
    if (!isOriginFile) {
      file = oFile.id ? oFile : Object.entries(oFile).map(([id, name]) => ({ id, name }))[0]
    }
    let url = file.id
    const isCosUrl = url.startsWith('cos/')
    if (isCosUrl) {
      url = file.id.includes('.') ? file.id : `${file.id}${file.name}`
    }
    const attname = useAttname ? `?attname=${file.name}` : ''
    let res = `${this.domain()}${url}${attname}`
    if (isCosUrl) {
      res = `${res}${
        res.includes('?') ? '&response-content-disposition=attachment' : '?response-content-disposition=attachment'
      }`
    }
    return res
  },
  getAttach(map: any, item: any) {
    const arr = item.id.split(item.name)
    map[arr[0]] = item.name
  },
  getFileKey({ up, isLink, file }: any = {}) {
    const cmpFile = file || up
    const fileKey = cmpFile.fileKey
    const domain = this.domain()
    return isLink ? `${domain}${fileKey}` : fileKey
  },
  getUploadKey(_: any, file: { name: any }) {
    const today = newDate()
    const time = today.getTime()
    const key = 'cos/' + '/km/' + '/' + time + '/' + file.name
    return key
  },
  getCosKey(key: any) {
    return `cos/${key}`
  }
}

export const common = {
  // 除了km外的其他模块cos上传方法
  getLinkUrl(oFile: any | ArrayLike<unknown>, options: any = {}) {
    // 获取链接
    const { useAttname = true, up, useDomain = true } = options
    const domain = up.getOption('domain')
    const file = oFile.id ? oFile : Object.entries(oFile).map(([id, name]) => ({ id, name }))[0]
    const url = file.id.includes('.') ? file.id : `${file.id}${file.name}`
    const attname = useAttname ? `?attname=${file.name}` : ''
    let res = `${useDomain ? domain : false}${url}${attname}`
    res = `${res}${
      res.includes('?') ? '&response-content-disposition=attachment' : '?response-content-disposition=attachment'
    }`
    return res
  },
  getFileKey({ up, isLink, file }: any = {}) {
    const domain = up.getOption('domain')
    const cmpFile = file || up
    const fileKey = cmpFile.fileKey
    return isLink ? `${domain}${fileKey}` : fileKey
  },
  getUrl(file: any, options: any) {
    const { useAttname = false, noQuery = false, domainKey = '', idKey = 'id' } = options || {}
    const cmpDomain = store.fileDomain[domainKey]
    const id = file[idKey]
    const cmpId = (String(id) || '').startsWith('http') ? id : file.name
    const isFullUrl = (String(id) || '').startsWith('http') || (file.name || '').startsWith('http')
    const cmpImgQuery = IMAGE_TYPE.some((i) => (file.type || '').includes(i)) ? 'imageView2/1/w/50' : ''
    const cmpUseAttname = useAttname
    const query = noQuery ? '' : `?${cmpUseAttname ? `attname=${file.name}` : cmpImgQuery}`
    let res = `${transUrl(isFullUrl ? `${cmpId}${query}` : `${cmpDomain}${id}${query}`)}`
    if (!noQuery) {
      res = `${res}${
        res.includes('?') ? '&response-content-disposition=attachment' : '?response-content-disposition=attachment'
      }`
    }
    return res
  },
  addAttname(attach: Record<string, unknown>) {
    return `${attach.url}?attname=${attach.name}`
  },
  addCosDown(url: string, ignoreImg: boolean) {
    const isImg = checkImgBySuffix(url)
    if (isImg && ignoreImg) {
      return url
    }
    const res = `${url}${
      url.includes('?') ? '&response-content-disposition=attachment' : '?response-content-disposition=attachment'
    }`
    return res
  }
}
